package cfar.parallel.reduce;

import cfar.ff.data.Assocs.Assoc2;
import cfar.ff.data.List;
import cfar.ff.data.Maybe;
import cfar.ff.fun.F1;
import cfar.ff.fun.F2;
import cfar.ff.interfaces.Sequence;

import cfar.parallel.map.ParMap;
import static cfar.ff.data.Maybe.*;
import static cfar.ff.fun.Seq.*;

/**
 *
 * @author badi
 */
public class foldl1<S, A, B> implements F2<F2<A, A, A>, Sequence<S, A>, A> {

    public A f(final F2<A, A, A> f, Sequence<S, A> seq) {
        Sequence<S, A> res = seq;
        F1<Assoc2<A, A>, A> _f = new F1<Assoc2<A, A>, A>() {

            public A f(Assoc2<A, A> a) {
                return f.f(a.$1, a.$2);
            }
        };

        do {
            Assoc2<List<Assoc2<A, A>>, Maybe<A>> assoc = mkAssocs(res);
            Sequence<S, A> partially_reduced = (Sequence<S, A>) ParMap.map(_f, assoc.$1);

            res = assoc.$2.isJust
                    ? partially_reduced.snoc(assoc.$2.fromJust())
                    : partially_reduced
                    ;
        } while (length(res) != 1);

        return head(res);
    }

    /**
     *
     * @param <A>
     * @param seq
     * @return a tuple of 1) a list of tuples and 2) the last value if seq had an odd number of elements
     */
    private <A> Assoc2<List<Assoc2<A, A>>, Maybe<A>> mkAssocs(Sequence<S, A> seq) {
        List<Assoc2<A, A>> l = List.nil();

        int need = 2;
        Maybe<A> tmp = nothing();
        for (A a : seq) {
            if (need == 2) {
                tmp = just(a);
                need--;
                continue;
            } else if (need == 1) {
                l = l.snoc(new Assoc2<A, A>(tmp.fromJust(), a));
                tmp = nothing();
                need = 2;
                continue;
            } else {
                throw new RuntimeException("The impossible occured!");
            }
        }

        // if seq had an odd number elements tmp should be 'just'
        return new Assoc2<List<Assoc2<A, A>>, Maybe<A>>(l, tmp);
    }
}
